package com.jprocms.module.cms.controller.portal.api;

import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.jprocms.module.cms.controller.admin.investigate.vo.InvestigateListReqVO;
import com.jprocms.module.cms.controller.admin.investigate.vo.InvestigateRespVO;
import com.jprocms.module.cms.controller.directive.InvestigateListDirective;
import com.jprocms.module.cms.convert.investigate.InvestigateConvert;
import com.jprocms.module.cms.dal.dataobject.investigate.InvestigateDO;
import com.jprocms.module.cms.dal.dataobject.investigateitem.InvestigateItemDO;
import com.jprocms.module.cms.dal.dataobject.investigateitemoption.InvestigateItemOptionDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.service.investigate.InvestigateOpinionService;
import com.jprocms.module.cms.service.investigate.InvestigateService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.QueryUtils;
import com.jprocms.module.cms.util.RequestUtils;
import com.jprocms.module.cms.web.Directives;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.constant.CmsSysConstants.FRONT_API;
import static com.jprocms.module.cms.constant.VoteConstants.*;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;
import static com.jprocms.module.cms.web.Directives.getOrderItems;

/**
 * 调查问卷前台 接口
 *
 * @author jporcms
 */
@Tag(name = "调查问卷接口", description = "调查问卷接口")
@RestController
@RequestMapping({FRONT_API + "/investigate"})
public class InvestigateApiController {
    @Resource
    private InvestigateService service;
    @Resource
    private InvestigateOpinionService investigateOpinionService;

    private <T> T query(HttpServletRequest request, BiFunction<InvestigateListReqVO, Map<String, String>, T> handle) {
        SiteDO site = CmsContextUtil.getSite();
        InvestigateListReqVO args = new InvestigateListReqVO();
        Map<String, String> params = QueryUtils.getParams(request.getQueryString());
        InvestigateListDirective.assemble(args, params, site.getId());
        return handle.apply(args, params);
    }

    @Operation(summary = "获取调查问卷列表")
    @Parameter(in = ParameterIn.QUERY, name = "siteId", description = "站点ID。默认为当前站点",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "isWithinDate", description = "是否在调查期限内。如：`true` `false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "isAllSite", description = "是否获取所有站点调查问卷。如：`true` `false`，默认`false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "offset", description = "从第几条数据开始获取。默认为0，即从第一条开始获取",
            schema = @Schema(type = "long", format = "long"))
    @Parameter(in = ParameterIn.QUERY, name = "limit", description = "共获取多少条数据。最大不能超过1000",
            schema = @Schema(type = "long", format = "long"))
    @GetMapping
    public CommonResult<List<InvestigateRespVO>> list(HttpServletRequest request) {
        return query(request, (args, params) -> {
            int offset = Directives.getOffset(params);
            int count = Directives.getCount(params);
            args.setOffset(offset);
            args.setCount(count);
            Collection<OrderItem> orderItems = getOrderItems(Arrays.asList("sort_num_asc", "id_asc"));
            args.setOrderItems(orderItems);
            List<InvestigateDO> list = service.getInvestigateList(args);
            List<InvestigateRespVO> investigateRespVOS = InvestigateConvert.INSTANCE.convertList(list);
            return CommonResult.success(investigateRespVOS);
        });
    }

    @Operation(summary = "获取调查问卷分页")
    @Parameter(in = ParameterIn.QUERY, name = "siteId", description = "站点ID。默认为当前站点",
            schema = @Schema(type = "integer", format = "int32"))
    @Parameter(in = ParameterIn.QUERY, name = "isActive", description = "是否在调查期限内。如：`true` `false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "allSite", description = "是否获取所有站点调查问卷。如：`true` `false`，默认`false`",
            schema = @Schema(type = "boolean"))
    @Parameter(in = ParameterIn.QUERY, name = "page", description = "第几页",
            schema = @Schema(type = "long", format = "long"))
    @Parameter(in = ParameterIn.QUERY, name = "pageSize", description = "每页多少条数据。最大不能超过1000",
            schema = @Schema(type = "long", format = "long"))
    @GetMapping("/page")
    public CommonResult<PageResult<InvestigateRespVO>> page(HttpServletRequest request) {
        return query(request, (args, params) -> {
            int page = Directives.getPage(params);
            int pageSize = Directives.getPageSize(params);
            args.setPageNo(page);
            args.setPageSize(pageSize);
            Collection<OrderItem> orderItems = getOrderItems(Arrays.asList("sort_num_asc", "id_asc"));
            args.setOrderItems(orderItems);
            return CommonResult.success(InvestigateConvert.INSTANCE.convertPage(service.getInvestigatePage(args)));
        });
    }

    @Operation(summary = "获取调查问卷对象")
    @ApiResponses(value = {@ApiResponse(description = "调查问卷对象")})
    @GetMapping("/{id:[\\d]+}")
    public CommonResult<InvestigateRespVO> get(@Parameter(description = "调查问卷ID") @PathVariable Long id) {
        InvestigateDO bean = service.getInvestigate(id);
        validateInvestigate(id, bean);
        return CommonResult.success(InvestigateConvert.INSTANCE.convert(bean));
    }

    public static void validateInvestigate(Long id, InvestigateDO bean) {
        if (bean == null) {
            throw exception(INVESTIGATE_NOT_EXISTS);
        }
        if (Boolean.FALSE.equals(bean.getEnabled())) {
            throw exception(INVESTIGATE_NOT_ENABLE);
        }
    }

    @Operation(summary = "投票")
    @ApiResponse(description = "msg: 提示信息。code: 状态码。0，成功；其他失败，失败消息在msg")
    @PostMapping("/vote")
    public CommonResult<Boolean> vote(@RequestBody @Valid InvestigateApiController.InvestigateParams params,
                                      HttpServletRequest request, HttpServletResponse response) {
        InvestigateDO investigate = service.getInvestigate(params.id);
        if (investigate == null) {
            throw exception(INVESTIGATE_NOT_EXISTS);
        }
        switch (investigate.getStatus()) {
            case STATUS_DISABLED:
                throw exception(INVESTIGATE_NOT_ENABLE);
            case STATUS_NOT_STARTED:
                throw exception(INVESTIGATE_NOT_START);
            case STATUS_HAS_ENDED:
                throw exception(INVESTIGATE_HAS_ENDED);
            default:
        }
        SiteDO site = CmsContextUtil.getSite();
        long cookie = RequestUtils.resolveIdentityCookie(request, response);
        String ip = RequestUtils.getRemoteAddr(request);
        Long userId = Optional.ofNullable(SecurityFrameworkUtils.getLoginUserId()).orElse(null);
        OffsetDateTime dateOf = investigate.getInvestigateInterval() > 0 ? OffsetDateTime.now().minusDays(investigate.getInvestigateInterval()) : null;
        Date date = Date.from(dateOf.toInstant());
        // 已经投过票
        boolean voted;
        switch (investigate.getInvestigateMode()) {
            case MODE_COOKIE:
                voted = investigateOpinionService.existsBy(investigate.getId(), date, null, null, cookie);
                break;
            case MODE_IP:
                voted = investigateOpinionService.existsBy(investigate.getId(), date, null, ip, null);
                break;
            case MODE_USER:
                if (userId == null) {
                    throw exception(INVESTIGATE_NEED_LOGIN);
                }
                voted = investigateOpinionService.existsBy(investigate.getId(), date, userId, null, null);
                break;
            default:
                throw exception(INVESTIGATE_NOT_SUPPORT_MODE);
        }
        if (voted) {
            throw exception(INVESTIGATE_REPEAT);
        }
        Map<Long, List<Long>> optionMap = getOptionMap(investigate, params.items);
        Map<Long, String> itemAswMap = getItemAswMap(investigate, params.items);
        service.vote(params.id, optionMap, itemAswMap, site.getId(), userId, ip, cookie);
        return CommonResult.success(true);
    }

    private Map<Long, List<Long>> getOptionMap(InvestigateDO investigate, Map<String, Object> items) {
        Map<Long, List<Long>> optionMap = new HashMap<>(16);
        for (InvestigateItemDO item : investigate.getItems()) {
            if (Boolean.TRUE.equals(item.getIsQa())) {
                continue;
            }
            Object value = items.get(String.valueOf(item.getId()));
            List<Long> optionIds;
            if (value instanceof String[]) {
                if (Boolean.FALSE.equals(item.getIsMultiple())) {
                    throw exception(INVESTIGATE_NOT_MULTIPLE);
                }
                optionIds = Arrays.stream((String[]) value).map(Long::valueOf).collect(Collectors.toList());
            } else if (value instanceof String) {
                optionIds = Collections.singletonList(Long.valueOf((String) value));
            } else {
                throw exception(INVESTIGATE_OPTION_ERROR);
            }
            List<Long> itemOptionsIds = item.getOptions().stream().map(InvestigateItemOptionDO::getId)
                    .collect(Collectors.toList());
            for (Long optionId : optionIds) {
                if (!itemOptionsIds.contains(optionId)) {
                    throw exception(INVESTIGATE_ITEM_OPTION_ERROR);
                }
            }
            optionMap.put(item.getId(), optionIds);
        }
        return optionMap;
    }

    private Map<Long, String> getItemAswMap(InvestigateDO investigate, Map<String, Object> items) {
        Map<Long, String> itemAswMap = new HashMap<>(16);
        for (InvestigateItemDO item : investigate.getItems()) {
            if (Boolean.FALSE.equals(item.getIsQa())) {
                continue;
            }
            Object value = items.get(String.valueOf(item.getId()));
            if (!(value instanceof String)) {
                throw exception(INVESTIGATE_ASW_TYPE_ERROR);
            }
            if (StringUtils.isBlank((String) value)) {
                throw exception(INVESTIGATE_ASW_REQUIRED);
            }
            itemAswMap.put(item.getId(), (String) value);
        }
        return itemAswMap;
    }

    public static class InvestigateParams {
        @NotNull
        private Long id;
        @NotNull
        private Map<String, Object> items;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Map<String, Object> getItems() {
            return items;
        }

        public void setItems(Map<String, Object> items) {
            this.items = items;
        }
    }
}
